package org.erikaredmark.monkeyshines.editor.dialog; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.JDialog; import javax.swing.JFileChooser; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.SpringLayout; import javax.swing.JLabel; import javax.swing.JTextField; import javax.swing.JButton; import java.awt.Dimension; import java.awt.event.ActionEvent; import java.awt.event.FocusEvent; import java.awt.event.FocusListener; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import javax.swing.JCheckBox; import org.erikaredmark.monkeyshines.encoder.WorldIO; import org.erikaredmark.monkeyshines.encoder.exception.WorldSaveException; import org.erikaredmark.util.BinaryLocation; /** * * Allows user to set settings for the newly generated world, and defers to utility class for carrying out creating the * structure asked for * * @author TJS * */ public class NewWorldDialog extends JPanel { private static final long serialVersionUID = 1L; private final JDialog parentDialog; private final JTextField txtWorldName; private final JTextField txtResourcePack; private final JCheckBox chckbxUseDefaultPack; private final JButton btnBrowseResourcePack; private final NewWorldDialogModel model; private final Controller controller; private final Action btnResourcePackAction = new AbstractAction("Browse...") { private static final long serialVersionUID = 6168460348517517770L; @Override public void actionPerformed(ActionEvent e) { JFileChooser fileChooser = new JFileChooser(); fileChooser.setCurrentDirectory(BinaryLocation.BINARY_LOCATION.toFile() ); if (fileChooser.showSaveDialog(null) == JFileChooser.APPROVE_OPTION) { File file = fileChooser.getSelectedFile(); Path rsrcPath = file.toPath(); model.setSelectedResourcePack(rsrcPath); txtResourcePack.setText(rsrcPath.toString() ); } } }; private final Action useDefaultAction = new AbstractAction("Use Default Pack") { private static final long serialVersionUID = 5848943844967927018L; @Override public void actionPerformed(ActionEvent e) { boolean selected = chckbxUseDefaultPack.isSelected(); model.setUseDefaultPack(selected); // Enable or disable path and browse text and buttons txtResourcePack.setEnabled(!(selected) ); btnBrowseResourcePack.setEnabled(!(selected) ); } }; private final Action saveAction = new AbstractAction("Save") { private static final long serialVersionUID = 3229707262753672755L; @Override public void actionPerformed(ActionEvent e) { JFileChooser fileChooser = new JFileChooser(); fileChooser.setCurrentDirectory(BinaryLocation.BINARY_LOCATION.toFile() ); if (fileChooser.showSaveDialog(null) == JFileChooser.APPROVE_OPTION) { File file = fileChooser.getSelectedFile(); Path savePath = file.toPath(); String worldName = model.getWorldName(); try { if (model.isUseDefaultPack() ) { WorldIO.newWorldWithDefault(savePath, worldName); } else { Path rsrcPath = model.getSelectedResourcePack(); if (rsrcPath == null) { showResourcePackError("No resource pack selected."); return; } if (!(Files.exists(rsrcPath) ) ) { showResourcePackError("Location does not exist: "); return; } if (Files.isDirectory(rsrcPath) ) { showResourcePackError("Location is a directory: "); return; } try { WorldIO.newWorldWithResources(savePath, worldName, rsrcPath); } catch (WorldSaveException ex) { showResourcePackError("Issue with resource pack: " + ex.getMessage() + " for pack: "); // TODO logging API ex.printStackTrace(); return; } } // Postcondition: World is saved. Dialog is no longer relevant: close it. // Save the state of what the user chose (the new file) model.saveLocation = savePath.resolve(worldName + ".world"); parentDialog.setVisible(false); // dispose(); // setVisible(false); // Dispatch event so system knows to actually close the dialog // dispatchEvent(new ComponentEvent(NewWorldDialog.this, ComponentEvent.COMPONENT_HIDDEN)); // These catch clauses do not deal with resource pack issues, only other, more general ones. } catch (IOException | WorldSaveException ex) { JOptionPane.showMessageDialog(null, "Unable to save due to " + ex.getMessage(), "Saving Error", JOptionPane.ERROR_MESSAGE); ex.printStackTrace(); } } } }; /** * * Shows an error dialog intended to alert the user that the resource pack selected is invalid for some reason. * * @param msg * the reason * */ private void showResourcePackError(String msg) { JOptionPane.showMessageDialog( null, msg + ( model.getSelectedResourcePack() != null ? model.getSelectedResourcePack().toString() : ""), "Resource Pack Issue", JOptionPane.ERROR_MESSAGE); } /** * * Creates a new instance of this dialog and allows the user to create a new world. This method is * NOT blocking. The dialog is initialised with its model but must be lauched. After the dialog * is closed, it will have populated a result in the model that indicates where the new world * was saved to, if it was saved at all. Clients may wish to query the model for that and * any other state information after the dialog has been launched and closed. * * @param parentDialog * the dialog object that is directly supporting this dialog (since this is * technically a JComponent). Used to provide early dialog closure * */ public NewWorldDialog(final JDialog parentDialog) { this.parentDialog = parentDialog; SpringLayout springLayout = new SpringLayout(); setLayout(springLayout); JLabel lblWorldName = new JLabel("World Name"); springLayout.putConstraint(SpringLayout.NORTH, lblWorldName, 13, SpringLayout.NORTH, this); springLayout.putConstraint(SpringLayout.WEST, lblWorldName, 10, SpringLayout.WEST, this); springLayout.putConstraint(SpringLayout.EAST, lblWorldName, -370, SpringLayout.EAST, this); add(lblWorldName); JLabel lblResourcePack = new JLabel("Resource Pack"); springLayout.putConstraint(SpringLayout.NORTH, lblResourcePack, 33, SpringLayout.SOUTH, lblWorldName); springLayout.putConstraint(SpringLayout.WEST, lblResourcePack, 10, SpringLayout.WEST, this); springLayout.putConstraint(SpringLayout.EAST, lblResourcePack, 0, SpringLayout.EAST, lblWorldName); add(lblResourcePack); txtWorldName = new JTextField(); springLayout.putConstraint(SpringLayout.NORTH, txtWorldName, 0, SpringLayout.NORTH, lblWorldName); springLayout.putConstraint(SpringLayout.WEST, txtWorldName, 6, SpringLayout.EAST, lblWorldName); springLayout.putConstraint(SpringLayout.EAST, txtWorldName, 236, SpringLayout.EAST, lblWorldName); add(txtWorldName); txtWorldName.setColumns(10); txtWorldName.addFocusListener(new FocusListener() { @Override public void focusGained(FocusEvent arg0) { /* no op */ } @Override public void focusLost(FocusEvent arg0) { // Losing focus basically means update the world name internally. We don't want to update on a char by char basis // as that would be bloody slow model.setWorldName(txtWorldName.getText() ); } }); txtResourcePack = new JTextField(); // Only can be changed via browse: Minor TODO to allow typed paths txtResourcePack.setEditable(false); springLayout.putConstraint(SpringLayout.NORTH, txtResourcePack, 0, SpringLayout.NORTH, lblResourcePack); springLayout.putConstraint(SpringLayout.WEST, txtResourcePack, 6, SpringLayout.EAST, lblResourcePack); springLayout.putConstraint(SpringLayout.EAST, txtResourcePack, 236, SpringLayout.EAST, lblResourcePack); add(txtResourcePack); txtResourcePack.setColumns(10); btnBrowseResourcePack = new JButton(btnResourcePackAction); springLayout.putConstraint(SpringLayout.NORTH, btnBrowseResourcePack, 0, SpringLayout.NORTH, txtResourcePack); springLayout.putConstraint(SpringLayout.WEST, btnBrowseResourcePack, 6, SpringLayout.EAST, txtResourcePack); springLayout.putConstraint(SpringLayout.EAST, btnBrowseResourcePack, -10, SpringLayout.EAST, this); add(btnBrowseResourcePack); chckbxUseDefaultPack = new JCheckBox(useDefaultAction); springLayout.putConstraint(SpringLayout.NORTH, chckbxUseDefaultPack, 6, SpringLayout.SOUTH, txtResourcePack); springLayout.putConstraint(SpringLayout.WEST, chckbxUseDefaultPack, 372, SpringLayout.WEST, this); add(chckbxUseDefaultPack); JButton btnSave = new JButton(saveAction); springLayout.putConstraint(SpringLayout.WEST, btnSave, 420, SpringLayout.WEST, this); springLayout.putConstraint(SpringLayout.SOUTH, btnSave, -10, SpringLayout.SOUTH, this); springLayout.putConstraint(SpringLayout.EAST, btnSave, 0, SpringLayout.EAST, btnBrowseResourcePack); add(btnSave); model = NewWorldDialogModel.newInstance(); controller = new Controller(model); setPreferredSize(new Dimension(500, 158) ); setSize(500, 200); } /** * * Returns the model associated with this view. * * @return */ public NewWorldDialogModel getModel() { return this.model; } /** * * Removes all listener bindings between the view and model, effectively killing this view. Only when use when view is no * longer accessible. * */ public void dispose() { controller.dispose(); } /** * * Each view creates one controller that has access to all private fields. This controller handles * model->view interaction. View -> model interaction is done directly * <p/> * Interaction here, for obvious reasons, do not modify the model (since the data comes from the model) * hence there will be no circularity to property changes. * * @author Erika Redmark * */ private final class Controller implements PropertyChangeListener { final NewWorldDialogModel model; Controller(final NewWorldDialogModel model) { this.model = model; this.model.addPropertyChangeListener(this); } @Override public void propertyChange(PropertyChangeEvent event) { switch (event.getPropertyName()) { case NewWorldDialogModel.PROPERTY_DEFAULT_PACK: break; case NewWorldDialogModel.PROPERTY_PACK: break; case NewWorldDialogModel.PROPERTY_WORLD_NAME: break; } } void dispose() { this.model.removePropertyChangeListener(this); } } }